//
// Created by jack on 20-3-12.
//
#include "com_voistech_sbccoder_SBCCodec.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "SBCLog.h"
#include "sbc.h"

using namespace std;

#ifdef __cplusplus
extern "C" {
#endif

static JNINativeMethod gJni_Methods_table[] = {
        {"sbcInit", "(IIIIII)Z", (void *)Java_com_voistech_sbccoder_SBCCodec_sbcInit},
        {"sbcDeInit", "()Z", (void *)Java_com_voistech_sbccoder_SBCCodec_sbcDeInit},
        {"sbcEncode", "([SI[BI[I)I", (void *)Java_com_voistech_sbccoder_SBCCodec_sbcEncode},
        {"sbcDecode", "([BI[SI[I)I", (void *)Java_com_voistech_sbccoder_SBCCodec_sbcDecode},
        {"sbcGetFrameLength", "()I", (void *)Java_com_voistech_sbccoder_SBCCodec_sbcGetFrameLength},
        {"sbcGetFrameDuration", "()I", (void *)Java_com_voistech_sbccoder_SBCCodec_sbcGetFrameDuration},
        {"sbcGetCodeSize", "()I", (void *)Java_com_voistech_sbccoder_SBCCodec_sbcGetCodeSize},
        {"sbcFinish", "()V", (void *)Java_com_voistech_sbccoder_SBCCodec_sbcFinish}
};


sbc_t *getSbcCtx(JNIEnv *env, jobject obj) {
    jclass clazz;
    jfieldID sbcCtxFieldId;
    jlong instance;
    clazz = (env)->GetObjectClass(obj);
    sbcCtxFieldId = (env)->GetFieldID(clazz, "sbcHandle", "J");
    instance = (env)->GetLongField(obj, sbcCtxFieldId);
    return (sbc_t *)(instance);
}

void setSbcCtx(JNIEnv *env, jobject obj, sbc_t *sbcCtx) {
    jclass clazz;
    jfieldID sbcCtxFieldId;
    jlong instance;
    clazz = (env)->GetObjectClass(obj);
    sbcCtxFieldId = (env)->GetFieldID(clazz, "sbcHandle", "J");
    (env)->SetLongField(obj, sbcCtxFieldId, (jlong)sbcCtx);
}


JNIEXPORT jboolean JNICALL Java_com_voistech_sbccoder_SBCCodec_sbcInit
        (JNIEnv *env, jobject obj, jint sampleRate, jint blocks,
                jint channelMode, jint allocMethod, jint subbands, jint bitpool)
{
    sbc_t *sbcCtx = nullptr;
    sbcCtx = (sbc_t *)malloc(sizeof(sbc_t));
    if (sbcCtx != nullptr) {
        if (sbc_init(sbcCtx, 0L) == 0) {
            sbcCtx->frequency = (uint8_t) sampleRate;
            sbcCtx->allocation = (uint8_t) allocMethod;
            sbcCtx->blocks = (uint8_t) blocks;
            sbcCtx->mode = (uint8_t) channelMode;
            sbcCtx->subbands = (uint8_t) subbands;
            sbcCtx->bitpool = (uint8_t) bitpool;
            setSbcCtx(env, obj, sbcCtx);
            return JNI_TRUE;
        }
    }

    return JNI_FALSE;
}

JNIEXPORT jboolean JNICALL Java_com_voistech_sbccoder_SBCCodec_sbcDeInit
        (JNIEnv *env, jobject obj)
{
    sbc_t *sbcCtx = getSbcCtx(env, obj);

    if (sbcCtx != nullptr)
    {
        if (sbc_reinit(sbcCtx, 0L) == 0)
            return JNI_TRUE;
    }

    return JNI_FALSE;
}

JNIEXPORT jint JNICALL Java_com_voistech_sbccoder_SBCCodec_sbcEncode
        (JNIEnv *env, jobject obj, jshortArray inputData, jint inputLen,
                jbyteArray outputData, jint outputLen, jintArray written)
{
    sbc_t *sbcCtx = getSbcCtx(env, obj);
    int ret = -1;

    if (sbcCtx != nullptr) {
        jboolean isCopy = JNI_FALSE;
        jshort *localInputData = nullptr;
        jbyte *localOutputData = nullptr;
        jint *localWritten = nullptr;

        localInputData = (env)->GetShortArrayElements(inputData, &isCopy);
        localOutputData = (env)->GetByteArrayElements(outputData, &isCopy);
        localWritten = (env)->GetIntArrayElements(written, &isCopy);

        if (localInputData != nullptr && localOutputData != nullptr && localWritten != nullptr) {
            jint len = 0;
            jbyte *byteInput = (jbyte *)malloc(inputLen * sizeof(jshort));
            memcpy(byteInput, localInputData, sizeof(jshort) * inputLen);
            (env)->ReleaseShortArrayElements(inputData, localInputData, 0);
            ret = sbc_encode(sbcCtx, byteInput, inputLen * 2, localOutputData, outputLen, &len);
            localWritten[0] = len;
            delete byteInput;
            (env)->ReleaseByteArrayElements(outputData, localOutputData, 0);
            (env)->ReleaseIntArrayElements(written, localWritten, 0);
        }
    }

    return ret;
}

JNIEXPORT jint JNICALL Java_com_voistech_sbccoder_SBCCodec_sbcDecode
        (JNIEnv *env, jobject obj, jbyteArray inputData, jint inputLen,
        jshortArray outputData, jint outputLen, jintArray len)
{
    sbc_t *sbcCtx = getSbcCtx(env, obj);
    int ret = -1;

    if (sbcCtx != nullptr) {
        jboolean isCopy = JNI_FALSE;
        jbyte *localInputData = nullptr;
        jshort *localOutputData = nullptr;
        jint *localWritten = nullptr;

        localInputData = (env)->GetByteArrayElements(inputData, &isCopy);
        localOutputData = (env)->GetShortArrayElements(outputData, &isCopy);
        localWritten = (env)->GetIntArrayElements(len, &isCopy);

        if (localInputData != nullptr && localOutputData != nullptr && localWritten != nullptr) {
            jint localLen = 0;
            jbyte *byteOutput = (jbyte *)malloc(sizeof(jshort) * outputLen);
            ret = sbc_decode(sbcCtx, localInputData, inputLen, byteOutput, outputLen * 2, &localLen);
            memcpy(localOutputData, byteOutput, localLen);
            delete byteOutput;
            localWritten[0] = localLen / 2;
            (env)->ReleaseByteArrayElements(inputData, localInputData, 0);
            (env)->ReleaseShortArrayElements(outputData, localOutputData, 0);
            (env)->ReleaseIntArrayElements(len, localWritten, 0);
        }
    }

    return ret;
}

JNIEXPORT jint JNICALL Java_com_voistech_sbccoder_SBCCodec_sbcGetFrameLength
        (JNIEnv *env, jobject obj)
{
    sbc_t *sbcCtx = getSbcCtx(env, obj);
    int ret = -1;

    if (sbcCtx != nullptr)
    {
        ret = sbc_get_frame_length(sbcCtx);
    }

    return ret;
}

JNIEXPORT jint JNICALL Java_com_voistech_sbccoder_SBCCodec_sbcGetFrameDuration
        (JNIEnv *env, jobject obj)
{
    sbc_t *sbcCtx = getSbcCtx(env, obj);
    int ret = -1;

    if (sbcCtx != nullptr)
    {
        ret = sbc_get_frame_duration(sbcCtx);
    }

    return ret;
}

JNIEXPORT jint JNICALL Java_com_voistech_sbccoder_SBCCodec_sbcGetCodeSize
        (JNIEnv *env, jobject obj)
{
    sbc_t *sbcCtx = getSbcCtx(env, obj);
    int ret = -1;

    if (sbcCtx != nullptr)
    {
        ret = sbc_get_codesize(sbcCtx);
    }

    return ret;
}

JNIEXPORT void JNICALL Java_com_voistech_sbccoder_SBCCodec_sbcFinish
        (JNIEnv *env, jobject obj)
{
    sbc_t *sbcCtx = getSbcCtx(env, obj);

    if (sbcCtx != nullptr)
    {
        sbc_finish(sbcCtx);
    }
}

static int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    SBC_LOG_I("JNI","Registering %s natives\n", className);
    clazz = (env)->FindClass( className);
    if (clazz == NULL) {
        SBC_LOG_E("JNI","Native registration unable to find class '%s'\n", className);
        return -1;
    }

    int result = 0;
    if ((env)->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        SBC_LOG_E("JNI","RegisterNatives failed for '%s'\n", className);
        result = -1;
    }

    (env)->DeleteLocalRef(clazz);

    return result;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved){
    SBC_LOG_I("JNI", "enter jni_onload");

    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }

    jniRegisterNativeMethods(env, "com/voistech/sbccoder/SBCCodec", gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));

    return JNI_VERSION_1_4;
}

#ifdef __cplusplus
}
#endif